package com.fansl.allround.auth.endpoint;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fansl.allround.common.core.constant.CommonConstants;
import com.fansl.allround.common.core.constant.SecurityConstants;
import com.fansl.allround.common.core.util.R;
import com.fansl.allround.common.security.service.AllroundSysUser;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.ConvertingCursor;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author fansl
 * @Description: 删除token端点
 * @date 2019/7/16 20:28
 */
@RestController
@AllArgsConstructor
@RequestMapping("/token")
public class AllroundTokenEndpoint {
    private static final String PROJECT_OAUTH_ACCESS = SecurityConstants.PROJECT_PREFIX + SecurityConstants.OAUTH_PREFIX + "access:";
    private static final String CURRENT = "current";
    private static final String SIZE = "size";
    private static final String THRID_PARTY_PARAM_CODE = "code";
    private static final String THRID_PARTY_PARAM_OAUTH_TYPE = "oauthType";
    private final TokenStore tokenStore;
    private final RedisTemplate redisTemplate;

    /**
     * 退出并删除token
     *
     * @param authHeader Authorization
     */
    @DeleteMapping("/logout")
    public R<Boolean> logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {
        if (StrUtil.isBlank(authHeader)) {
            return R.<Boolean>builder().code(CommonConstants.SUCCESS)
                    .data(Boolean.FALSE)
                    .msg("退出失败，token为空").build();
        }

        String tokenValue = authHeader.replace(OAuth2AccessToken.BEARER_TYPE, StrUtil.EMPTY).trim();
        OAuth2AccessToken auth2AccessToken = tokenStore.readAccessToken(tokenValue);
        if (auth2AccessToken == null || StrUtil.isBlank(auth2AccessToken.getValue())) {
            return R.<Boolean>builder().code(CommonConstants.SUCCESS)
                    .data(Boolean.FALSE)
                    .msg("退出失败，token无效").build();
        }

        tokenStore.removeAccessToken(auth2AccessToken);
        OAuth2RefreshToken refreshToken = auth2AccessToken.getRefreshToken();
        tokenStore.removeRefreshToken(refreshToken);
        return R.<Boolean>builder()
                .code(CommonConstants.SUCCESS)
                .data(Boolean.TRUE)
                .build();
    }

    /**
     * 令牌管理调用
     *
     * @param token token
     * @param from  内部调用标志
     */
    @DeleteMapping("/{token}")
    public R<Boolean> removeToken(@PathVariable("token") String token,
                                  @RequestHeader(required = false) String from) {
        if (StrUtil.isBlank(from)) {
            return null;
        }
        return new R<>(redisTemplate.delete(PROJECT_OAUTH_ACCESS + token));
    }

    /**
     * 物理分页查询token
     *
     * @param params 分页参数
     * @param from   内部调用标志
     * @return
     */
    @PostMapping("/page")
    public R getTokenPage(@RequestBody Map<String, Object> params,
                          HttpServletRequest request,
                          @RequestHeader(name = SecurityConstants.FROM ,required = false) String from) {
        if (StrUtil.isBlank(from)) {
            return null;
        }
        List<Map<String, String>> list = new ArrayList<>();
        if (StrUtil.isEmptyIfStr(MapUtil.getInt(params, CURRENT)) ||
                StrUtil.isEmptyIfStr(MapUtil.getInt(params, SIZE))) {
            params.put(CURRENT, 1);
            params.put(SIZE, 20);
        }
        //根据分页参数获取对应数据
        List<String> pages = findKeysForPage(PROJECT_OAUTH_ACCESS + "*", MapUtil.getInt(params, CURRENT), MapUtil.getInt(params, SIZE));

        for (String page : pages) {
            String accessToken = StrUtil.subAfter(page, PROJECT_OAUTH_ACCESS, true);
            OAuth2AccessToken token = tokenStore.readAccessToken(accessToken);
            Map<String, String> map = new HashMap<>(8);

            map.put(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType());
            map.put(OAuth2AccessToken.ACCESS_TOKEN, token.getValue());
            map.put(OAuth2AccessToken.EXPIRES_IN, String.valueOf(token.getExpiresIn()));

            OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(token);
            Authentication authentication = oAuth2Authentication.getUserAuthentication();

            map.put(OAuth2Utils.CLIENT_ID, oAuth2Authentication.getOAuth2Request().getClientId());
            map.put(OAuth2Utils.GRANT_TYPE, oAuth2Authentication.getOAuth2Request().getGrantType());
            Object principal = authentication.getPrincipal();
            if (principal instanceof AllroundSysUser) {
                AllroundSysUser user = (AllroundSysUser) principal;
                map.put(SecurityConstants.DETAILS_USER_ID, String.valueOf(user.getId()));
                map.put(SecurityConstants.DETAILS_USERNAME, user.getUsername());
            }
            list.add(map);
        }
        Page result = new Page(MapUtil.getInt(params, CURRENT), MapUtil.getInt(params, SIZE));
        result.setRecords(list);
        result.setTotal(Long.valueOf(redisTemplate.keys(PROJECT_OAUTH_ACCESS + "*").size()));
        return new R(result);
    }

    private List<String> findKeysForPage(String patternKey, int pageNum, int pageSize) {
        ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
        RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
        Cursor cursor = (Cursor) redisTemplate
                .executeWithStickyConnection(redisConnection ->
                        new ConvertingCursor<>(redisConnection.scan(options),
                                redisSerializer::deserialize)
                );
        List<String> result = new ArrayList<>();
        int tempIndex = 0;
        int startIndex = (pageNum - 1) * pageSize;
        int end = pageNum * pageSize;
        Assert.notNull(cursor, "token data is null");
        while (cursor.hasNext()) {
            if (tempIndex >= startIndex && tempIndex < end) {
                result.add(cursor.next().toString());
                tempIndex++;
                continue;
            }
            if (tempIndex >= end) {
                break;
            }
            tempIndex++;
            cursor.next();
        }
        return result;
    }
}
